package fr.labri.macchiato.framework.business.model.cart;

import java.util.ArrayList;
import java.util.List;

import fr.labri.macchiato.framework.business.model.product.Article;
import fr.labri.macchiato.framework.business.model.product.Model;
import fr.labri.macchiato.framework.business.model.product.Offer;
import fr.labri.macchiato.framework.business.model.product.Product;
import fr.labri.macchiato.framework.technical.repository.Identifiable;
import fr.labri.macchiato.framework.util.FrameworkException;
import fr.labri.macchiato.framework.util.MObservable;

/**
 * A cart is composed of {@link Product}s, and have a name
 * 
 * @author Matthieu FOUCAULT
 * 
 */
public class Cart extends MObservable implements Identifiable<String> {

	private static final String ERROR_NEGATIVE_BUDGET = "Le budget alloué doit être positif";
	private String name;
	private double budget;
	private List<Article> articles;
	private List<Model> models;
	private List<Offer> offers;

	Cart() {
		articles = new ArrayList<Article>();
		models = new ArrayList<Model>();
		offers = new ArrayList<Offer>();
		name = "";
		budget = 0;
	}

	Cart(String name) {
		this();
		this.setName(name);
	}

	Cart(String name, double budget){
	  this(name);
	  setBudget(budget);
	}
	
	/**
	 * Adds a product to the cart. <br>
	 * If the product is already in the cart, we increase its quantity
	 * 
	 * @param product The product to be added
	 */
	public void addProduct(Product product) {
		
		addProduct(product, true);
	}

	private void addProduct(Product product, boolean mustNotify) {
		
		Product productEntry = getCartProduct(product);
		if (productEntry != null) {
			// If the product is already in the cart, we increase its quantity
			productEntry.setQuantity(productEntry.getQuantity() + product.getQuantity());
			return;
		} else {
			// We add the new product
			if (product instanceof Article) {
				articles.add((Article) product);
			} else if (product instanceof Model)
				models.add((Model) product);

			else if (product instanceof Offer)
				offers.add((Offer) product);
		}
		
		if (mustNotify)
			notifyObservers(product);
	}
	
	/**
	 * Adds an ensemble of product to the cart
	 * 
	 * @param products The list of product to be added
	 */
	public void addProducts(List<Product> products) {
		
		for (Product p : products)
			addProduct(p, false);
		
		notifyObservers(products);
	}
	
	private Product getCartProduct(Product product) {
		
		List<? extends Product> products = null;
		if (product instanceof Article)
			products = articles;
		else if (product instanceof Model)
			products = models;
		else if (product instanceof Offer)
			products = offers;

		for (Product productEntry : products)
			if (productEntry.equals(product)) {
				return productEntry;
			}
		
		return null;
	}
	

	/**
	 * Removes a product from the cart, does nothing if the product isn't in the
	 * cart
	 * 
	 * @param product
	 */
	public void removeProduct(Product product) {

		if (product instanceof Article)
			getArticles().remove(product);
		else if (product instanceof Model)
			getModels().remove(product);
		else if (product instanceof Offer)
			getOffers().remove(product);
	}
	
	
	/**
	 * Removes all the products from the cart
	 */
	public void clearProducts() {
		getArticles().clear();
		getModels().clear();
		getOffers().clear();
		
		notifyObservers();
	}
	

	/**
	 * @return The number of products in the cart
	 */
	public int getSize() {
		return articles.size() + models.size() + offers.size();
	}

	/**
	 * 
	 * @return The total price of the {@link Offer}s if the cart
	 */
	public double getTotal() {
		double total = 0;
		for (Offer o : offers) {
			total += o.getQuantity() * o.getPrice();
		}
		return total;
	}

	/**
	 * Set the quantity of a product in the cart, does nothing if the product
	 * isn't in the cart
	 * 
	 * @param product
	 * @param quant
	 */
	public void setQuantity(Product product, int quant) {
		if (product instanceof Article)
			setQuantity(product, quant, getArticles());
		else if (product instanceof Model)
			setQuantity(product, quant, getModels());
		else if (product instanceof Offer)
			setQuantity(product, quant, getOffers());
		
		notifyObservers();
	}

	private void setQuantity(Product product, int quant,
			List<? extends Product> products) {

		for (Product productEntry : products)
			if (productEntry.equals(product)) {
				productEntry.setQuantity(quant);
				break;
			}
	}

	/**
	 * Returns the quantity of a product in this {@link Cart}
	 * 
	 * @param product
	 * @return the quantity of the product, 0 if it isn't in the cart
	 */
	public int getQuantity(Product product) {

		if (product instanceof Article)
			return getQuantity(product, getArticles());
		else if (product instanceof Model)
			return getQuantity(product, getModels());
		else if (product instanceof Offer)
			return getQuantity(product, getOffers());
		else
			return 0;
	}

	private int getQuantity(Product product,
			List<? extends Product> products) {

		for (Product productEntry : products)
			if (productEntry.equals(product))
				return productEntry.getQuantity();

		return 0;
	}

	/**
	 * 
	 * @return the name of the cart
	 */
	public String getName() {
		return name;
	}

	/**
	 * Sets the name of the cart
	 * 
	 * @param name
	 */
	public void setName(String name) {
		this.name = name;
	}
	
	/**
	 * 
	 * @return the cart's budget
	 */
	public double getBudget() {
		return budget;
	}

	/**
	 * Sets the cart budget
	 * @param budget
	 */
	public void setBudget(double budget) {
		if (budget < 0)
			throw new FrameworkException(ERROR_NEGATIVE_BUDGET);
		this.budget = budget;
		notifyObservers();
	}
	
	/**
	 * 
	 * @return The articles stored in the cart
	 */
	public List<Article> getArticles() {
		return articles;
	}

	/**
	 * Sets the articles
	 * 
	 * @param articles
	 */
	public void setArticles(List<Article> articles) {
		this.articles = articles;
	}

	/**
	 * 
	 * @return The models stored in the cart
	 */
	public List<Model> getModels() {
		return models;
	}

	/**
	 * Sets the models
	 * 
	 * @param models
	 */
	public void setModels(List<Model> models) {
		this.models = models;
	}

	/**
	 * 
	 * @return The offers stored in the cart
	 */
	public List<Offer> getOffers() {
		return offers;
	}

	/**
	 * Sets the offers
	 * 
	 * @param offers
	 */
	public void setOffers(List<Offer> offers) {
		this.offers = offers;
	}


	@Override
	public String getId() {
		return getName();
	}
}
